package ms.unicorn.dsl.runtime;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ms.unicorn.dsl.unicornDSL.XExpression;
import ms.unicorn.dsl.utils.SphynxTypeMismatchException;


public abstract class TypeConverter {
	
	private static Map<Class<?>, List<TypeConverter>> converters = new HashMap<Class<?>, List<TypeConverter>>();
	
	public static void registerConverter(Class<?> clazz, TypeConverter converter) {
		List<TypeConverter> list = converters.get(clazz);
		if (list == null)
			converters.put(clazz, list = new ArrayList<TypeConverter>());
		if (!list.contains(converter))
			list.add(converter);
	}
	
	protected static List<TypeConverter> getConverters(Class<?> target) {
		if (converters.containsKey(target))
			return converters.get(target);
		Converter annotation = target.getAnnotation(Converter.class);
		if (annotation != null)
			for (Class<? extends TypeConverter> type: annotation.converter())
				try {
					registerConverter(target, type.newInstance());
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
		return converters.get(target);
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T convert(SphynxInterpreter interpreter, ExecutionContext context, XExpression exp, Class<?> targetType) {
		Object value = interpreter.resolveValue(exp, context);
		try {
			return (T)convert(value, targetType);
		}
		catch(RuntimeException e) {
			throw new SphynxTypeMismatchException(context, exp, targetType, value);
		}
	}
	
	@SuppressWarnings("unchecked")
	public static Boolean convertToBoolean(SphynxInterpreter interpreter, ExecutionContext context, XExpression exp) {
		Object value = interpreter.resolveValue(exp, context);
		try {
			return (Boolean)convert(value, Boolean.class);
		}
		catch(RuntimeException e) {
			//throw new SphynxTypeMismatchException(context, exp, Boolean.class, value);
			return value != null;
		}
	}
	
	public static Object convert(Object value, Class<?> targetType) {
		Class<?> sourceType = value != null ? value.getClass() : null;
		if (sourceType != null)
			return convert(value, sourceType, targetType);
		throw new NullPointerException("value");
	}
	
	public static boolean canConvertTo(Object value, Class<?> targetType) {
		try {
			convert(value, targetType);
			return true;
		}
		catch(Exception e) {
			return false;
		}
	}
	
	@SuppressWarnings("null")
	public static Object convert(Object value, Class<?> sourceType, Class<?> targetType) {
		if (targetType.isInstance(value))
			return value;
		while (sourceType != null) {
			List<TypeConverter> list = getConverters(sourceType);
			if (list != null)
				for (TypeConverter converter: list)
					if (converter.canConvertTo(null, targetType))
						return converter.convertTo(value, sourceType, targetType);
			sourceType = sourceType.getSuperclass();
		}
		while (targetType != null) {
			List<TypeConverter> list = getConverters(targetType);
			if (list != null)
				for(TypeConverter converter: list)
					if (converter.canConvertFrom(sourceType, null))
						return converter.convertFrom(value, sourceType, targetType);
			targetType = targetType.getSuperclass();
		}
		throw new RuntimeException(String.format("Unable to convert '%s' to type '%s'", value, targetType.getName()));
	}
	
	
	public Object convertTo(Object value, Class<?> sourceType, Class<?> targetType) {
		if (targetType == String.class)
			return value.toString();
		return null;
	}
	
	public boolean canConvertTo(Class<?> sourceType, Class<?> targetType) {
		if (targetType == String.class)
			return true;
		return false;
	}
	
	public boolean canConvertFrom(Class<?> sourceType, Class<?> targetType) {
		return false;
	}
	
	public Object convertFrom(Object value, Class<?> sourceType, Class<?> targetType) {
		return null;
	}
}
